/*
 * Copyright (c) 2020-2020, org.smartboot. All rights reserved.
 * project name: smart-license
 * file name: LicenseServer.java
 * Date: 2020-03-22
 * Author: sandao (zhengjunweimail@163.com)
 */

package org.smartboot.license.server;


import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.IOUtils;
import org.smartboot.license.client.LicenseEntity;
import org.smartboot.license.client.Md5;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;

/**
 * @author 三刀
 * @version V1.0 , 2020/3/20
 */
public class LicenseServer {
    private static final String SOURCE_FILE = "source.txt";
    private static final String LICENSE_FILE = "license.txt";
    private static final String COMMAND_EXPIRE = "e";
    private static final String COMMAND_FILE = "f";
    private static final String COMMAND_TEXT = "t";
    private static final String COMMAND_PRIVATE_KEY = "k";

    private final File sourceFile;
    private final File licenseFile;

    public LicenseServer(File sourceFile, File licenseFile) {
        this.sourceFile = sourceFile;
        this.licenseFile = licenseFile;
    }
    
    public static void main(String[] paramArrayOfString) throws Exception {
    	paramArrayOfString=new String[] {"1y","GosunTrial",
    			"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJMBBj0k8ye0OS5FlnMQNQ3eg6Dz9dN9zDB8Qu1htYVFlkc5w/GcMajjAK19lZEGkotdCZime9FSQxmfaa9wXPsDwKO64K3Dtn/WxW3BWNVKTwDZe+zjBU+bjMdR03MDAU9CTnKiTXRSBaTDb1jH+2+6tQzqdHiNsY74KifFaF0/AgMBAAECgYA4mgJe9KUzOm9x45VkRG9JIc4o4QZcImnGmXHjmOWdpQeR8SoDE5MDZjRSmLJIHvG5+17AignOtQ/lA75bsLX70xc/dkhQCv5j7PBGD7dmQ5iB7gVAJJSaKL4ImSNaoM5ZHzEuTjxb79vaxr6BI8EMKFTTqSZmtmFwC9c+9aQYYQJBAMo50daMzLGEpVpbzZtICKKuRo8nKZdByp+elbi07TC7zjHa2wRvguwtV+iv6mcoO9fBVwNl9rV1VaUa5yMUBMkCQQC6GBIs9+vU9HJDO62+PymX9MD13y4f/Y/YkBhTOGpOb9ms/Ij+UbyZz1iJFmVJH9Gl8zQRCUYVGp6Et3L8Lv3HAkEAyLIDV8uaph6/v+JWNmKR92nAKvgx5aRsxGctrgcZUwFADOhn7ooAxAXoNczIdCJQ6bo+cefypXnSSq5db8nBSQJAF2f9qUBJnlBcqs4vzTT+k0lKl+JJV+a5mACPvq/i1xjEZTiiNnKraSEmPp/df3QMF3ZefyIOeYsOkxzLzqJOcwJAaJ5EkOlQfdUMV1ucK7hI+BAD4Z8tuyuqlPMQhyekRpNoZEQJNOBSCVpD/2gnPSQvHPq+z23g4rjpVt4DVBXn6A=="};
    	
        byte[] arrayOfByte;
        if (paramArrayOfString == null || paramArrayOfString.length < 2) {
//          LOGGER.info("args is invalid");
          System.out.println("args is invalid");
          return;
        } 
        LicenseServer licenseServer = new LicenseServer(new File(SOURCE_FILE), new File(LICENSE_FILE));
        String str1 = paramArrayOfString[0];
        char c = str1.charAt(str1.length() - 1);
        int i = Integer.valueOf(str1.substring(0, str1.length() - 1)).intValue();
        Calendar calendar = Calendar.getInstance();
        switch (c) {
          case 'H':
          case 'h':
            calendar.add(Calendar.HOUR, i);
            break;
          case 'D':
          case 'd':
            calendar.add(Calendar.DAY_OF_YEAR, i);
            break;
          case 'Y':
          case 'y':
            calendar.add(Calendar.YEAR, i);
            break;
          default:
            throw new UnsupportedOperationException(str1);
        } 
        String str2 = paramArrayOfString[1];
        File file = new File(str2);
        if (file.isFile()) {
//          LOGGER.info("sign for file:{}", file.getPath());
          System.out.println(String.format("sign for file:%s", file.getPath()));
          arrayOfByte = IOUtils.toByteArray(new FileInputStream(file));
        } else {
//          LOGGER.info("sign for string:{}", str2);
          System.out.println(String.format("sign for string:%s", str2));
          arrayOfByte = str2.getBytes();
        } 
        if (paramArrayOfString.length > 2) {
          licenseServer.createLicense(arrayOfByte, calendar.getTime(), Base64.getDecoder().decode(paramArrayOfString[2]));
        } else {
          licenseServer.createLicense(arrayOfByte, calendar.getTime());
        } 
      }

    public static void main1(String[] args) throws Exception {
//        System.out.println(Arrays.toString(args));
        CommandLine commandLine = parseCli(args);
        if (commandLine == null) {
            return;
        }

        LicenseServer license = new LicenseServer(new File(SOURCE_FILE), new File(LICENSE_FILE));
        String expire = commandLine.getOptionValue(COMMAND_EXPIRE);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date expireDate = sdf.parse(expire);
        String filePath = commandLine.getOptionValue(COMMAND_FILE);
        String text = commandLine.getOptionValue(COMMAND_TEXT);
        String key = commandLine.getOptionValue(COMMAND_PRIVATE_KEY);
        byte[] keyBytes = null;
        if (key != null) {
            try {
                keyBytes = Base64.getDecoder().decode(key);
            } catch (Exception e) {
                System.out.println("PrivateKey decode fail: " + e.getMessage());
                return;
            }
        }

        byte[] bytes;
        if (filePath != null) {
            File file = new File(filePath);
            if (!file.isFile()) {
                System.out.println("none file:" + file.getAbsolutePath());
                return;
            }
            System.out.println("sign for file:" + file.getPath());
            bytes = IOUtils.toByteArray(new FileInputStream(file));
        } else {
            if (text == null) {
                System.out.println("none text");
                return;
            }
            System.out.println("sign for string:" + text);
            bytes = text.getBytes();
        }
        if (keyBytes != null) {
            license.createLicense(bytes, expireDate, keyBytes);
        } else {
            license.createLicense(bytes, expireDate);
        }
    }

    private static CommandLine parseCli(String[] args) throws ParseException {
        Options options = new Options();
        options.addOption(COMMAND_FILE, "file", true, "file to be signed.");
        options.addOption(COMMAND_TEXT, "text", true, "text to be signed.");
        options.addOption(COMMAND_PRIVATE_KEY, "key", true, "encrypted private key, generated by keypair.");
        options.addOption(COMMAND_EXPIRE, "expire", true, "License expiration time, format: yyyy-MM-dd");
        CommandLineParser parser = new DefaultParser();
        CommandLine commandLine = parser.parse(options, args);
        if (commandLine.hasOption(COMMAND_FILE) && commandLine.hasOption(COMMAND_TEXT)) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("-f and -t cannot exist at the same time", options);
            return null;
        }
        if (!commandLine.hasOption(COMMAND_FILE) && !commandLine.hasOption(COMMAND_TEXT)) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("must specify -f or -t", options);
            return null;
        }
        if (!commandLine.hasOption(COMMAND_EXPIRE)) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("must specify -e", options);
            return null;
        }
        return commandLine;
    }

    /**
     * 采用非对称加密对data作预处理
     */
    public void createLicense(byte[] data, Date expireDate, byte[] privateKey) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int offset = 0;
        int step = 64;
        if (step > data.length) {
            step = data.length;
        }
        while (offset < data.length) {
            byte[] encryptData = RasUtil.encryptByPrivateKey(data, privateKey, offset, step);
            byteArrayOutputStream.write(encryptData.length);
            byteArrayOutputStream.write(encryptData);
            offset += step;
            step = Math.min(data.length - offset, step);
        }
        createLicense(byteArrayOutputStream.toByteArray(), expireDate);
    }

    /**
     * 生成License
     *
     * @param data       license内容
     * @param expireDate 过期时间
     */
    public void createLicense(byte[] data, Date expireDate) throws Exception {
        if (expireDate == null) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.YEAR, 100);
            expireDate = calendar.getTime();
        }
        //初始化密钥
        //生成密钥对
        KeyPair keyPair = RasUtil.initKey();

        LicenseEntity entity = new LicenseEntity(expireDate.getTime(), RasUtil.getPublicKey(keyPair), Md5.md5(data));
        entity.setData(data);
        createLicense(entity, keyPair);

        createSourceLicense(entity, keyPair);
    }

    private void createLicense(LicenseEntity entity, KeyPair keyPair) throws Exception {
        //生成License
        try (FileOutputStream fileOutputStream = new FileOutputStream(licenseFile)) {
            LicenseEncode licenseEncode = new LicenseEncode();
            fileOutputStream.write(Base64.getEncoder().encodeToString(licenseEncode.encode(entity, RasUtil.getPrivateKey(keyPair))).getBytes(StandardCharsets.UTF_8));
        }
    }

    /**
     * 生成License源文件
     */
    private void createSourceLicense(LicenseEntity entity, KeyPair keyPair) throws IOException {
        SourceLicense sourceLicense = new SourceLicense(entity, keyPair);
        //生成License
        try (FileWriter fileWriter = new FileWriter(sourceFile)) {
            Properties properties = new Properties();
            properties.setProperty(SourceLicense.PROPERTY_APPLY_DATE, sourceLicense.getApplyDate());
            properties.setProperty(SourceLicense.PROPERTY_EXPIRE_DATE, sourceLicense.getExpireDate());
            properties.setProperty(SourceLicense.PROPERTY_BASE64_CONTENT, sourceLicense.getBase64Content());
            properties.setProperty(SourceLicense.PROPERTY_PUBLIC_KEY, sourceLicense.getPublicKey());
            properties.put(SourceLicense.PROPERTY_PRIVATE_KEY, sourceLicense.getPrivateKey());
            properties.store(fileWriter, null);
        }
        System.out.println("Expire Date:" + sourceLicense.getExpireDate());
    }
}
